第八章 随机森林模型¶

  • 集成学习

    • 将多个模型组合在一起, 从而产生更强大的模型

    • 典型的集成学习模型: 随机森林模型

8.1 随机森林模型的原理和代码实现¶

8.1.1 集成模型简介¶

  • 集成学习模型使用一系列弱学习模型 (也称为基础模型或基模型) 进行学习, 并将各个弱学习器的结果进行整合, 从而获得比单个学习器更好的学习效果

  • 集成学习模型常见的算法有Bagging算法和Boosting算法两种

  • Bagging算法的典型机器学习模型就是随机森林模型

  • Boosting算法的典型机器学习模型有AdaBoost, GBDT, XGBoost, LightGBM模型

8.1.1.1 Bagging算法¶

Bagging算法的原理类似投票, 每个弱学习器都有一票, 最终根据所有弱学习器的投票, 按照 "少数服从多数" 的原则产生最终的预测结果

  • 基本流程

    • 假设原始数据共有10000条, 从中随机有放回地抽取10000次数据构成一个新的训练集 (抽取的训练集的大小为10000, 与原始数据相同, 由于是有放回, 在这10000条数据中可能有些数据是重复的, 而原始数据中有些可能不在新的训练集中)

    • 每次使用一个训练集训练一个弱学习器

      这样有放回地随机抽取 $n$ 次后, 训练结束时就能获得由不同地训练集训练出的 $n$ 个弱学习器, 根据这 $n$ 个弱学习器的预测结果, 按照 "少数服从多数" 的原则获取预测结果 (在分类问题中是是同投票的方式, 在回归问题中是取平均值)

8.1.1.2 Boosting算法¶

  • Boosting算法的本质是将弱学习器提升为强学习器, 与Bagging的算法的区别在Bagging算法对待所有的弱学习器一视同仁, 而Boosting算法则会对弱学习器 "区别对待"("培养精英" 和 "重视错误")

    • 培养精英: 每一轮训练后对预测结果较为准确的弱学习器给予较大的权重, 对表现不好的弱学习器则降低其权重, 这样在最终预测时, "优秀模型" 的权重是大的, 相当于它可以投出多票, 而 "一般模型" 只能投出一票或不能投票

    • 重视错误: 每一轮训练后改变训练集的权值或概率分布, 通过提高在前一轮被弱学习器预测错误的样例的权值, 降低前一轮被弱学习器预测正确的样例的权值来提高弱学习器对预测错误的数据的重视程度, 从而提升模型的整体预测效果

8.1.2 随机森林模型的基本原理¶

  • 随机森林模型是经典的Bagging模型, 其弱学习器为决策树模型

  • 随机森林模型会在原始数据集中随机抽样, 构成$n$个不同的样本数据集, 然后根据这些数据集搭建$n$个不同的决策树模型, 最后根据这些决策树模型的平均值或者投票情况来获取最终结果

  • 为了保证模型的泛化能力, 随机森林模型在建立每棵树时, 往往会遵循 "数据随机" 和 "特征随机" 这两个基本原则

    • 数据随机

      从所有数据中 有放回 地随机抽取数据作为其中一个决策树模型地训练数据, 例如有1000个原始数据, 有放回地抽取1000次, 构成一组新的数据, 用于训练某一个决策树模型

    • 特征随机

      如果每个样本的特征维度为$M$, 指定一个常数 $k < M$, 随机地从 $M$ 个特征中选取 $k$ 个特征, 在使用Python构造随机森林模型时, 默认选取特征的个数 $k$ 为 $\sqrt{M}$

    与单独的决策树模型相比, 随机森林模型由于继承了多个决策树, 其预测结果会更准确, 且不容易造成过拟合现象, 泛化能力更强

8.1.3 随机森林模型的代码实现¶

基于决策树 (随机森林模型的弱学习器) , 随机森林模型既能进行分类分析, 又能进行回归分析, 对应的模型分别为随机森林分类模型和随机森林回归模型

In [1]:
import warnings

warnings.filterwarnings('ignore')
In [2]:
# 随机森林分类模型简单代码演示如下所示:
from sklearn.ensemble import RandomForestClassifier
X = [[1, 2], [3, 4], [5, 6], [7, 8], [9, 10]]
y = [0, 0, 0, 1, 1]

model = RandomForestClassifier(n_estimators=10, random_state=123) #n_estimators=10 设置弱学习器的数量为10; random_state=123 设置随机种子 (数据随机和特征随机)
model.fit(X, y)

print(model.predict([[5, 5]]))
[0]
In [3]:
# 随机森林回归模型简单代码演示如下所示:
from sklearn.ensemble import RandomForestRegressor
X = [[1, 2], [3, 4], [5, 6], [7, 8], [9, 10]]
y = [1, 2, 3, 4, 5]

model = RandomForestRegressor(n_estimators=10, random_state=123)
model.fit(X, y)

print(model.predict([[5, 5]]))
[2.8]

8.2 案例实战: 股票涨跌预测模型¶

量化金融可分为通过大数据分析进行智能择时 (选择合适的交易时机) 与智能择股 (选择合适的交易股票) 两个主要方向, 这两个方向的本质都是预测股票的涨跌情况

8.2.1 股票基本数据获取¶

搭建机器学习模型的数据可以使用Tushare库来获取

8.2.1.1 获取日线行情数据¶

In [4]:
token = 'yourtoken' 
In [5]:
import tushare as ts

ts.set_token(token) #这里的token是个人账号的
In [6]:
pro = ts.pro_api()

pro = ts.pro_api(token)
In [7]:
# 准备查询参数
stock_code = '000002.SZ'  # 股票代码,注意要加上后缀.SZ 表示深交所股票
start_date = '20180101'
end_date = '20190131'
In [8]:
df = pro.daily(ts_code=stock_code, start_date=start_date, end_date=end_date) #如果不写开始日期和结束日期, 会默认获取当天往前3年的数据
In [9]:
df.head()
Out[9]:
ts_code trade_date open high low close pre_close change pct_chg vol amount
0 000002.SZ 20190131 27.39 28.15 27.00 27.75 27.21 0.54 1.9846 411857.60 1138512.393
1 000002.SZ 20190130 26.70 27.82 26.63 27.21 26.88 0.33 1.2277 592303.18 1615186.856
2 000002.SZ 20190129 25.91 26.88 25.87 26.88 26.06 0.82 3.1466 368071.63 974279.357
3 000002.SZ 20190128 26.20 26.62 25.86 26.06 26.10 -0.04 -0.1533 308906.56 810288.818
4 000002.SZ 20190125 25.51 26.35 25.49 26.10 25.41 0.69 2.7155 451756.17 1176479.676

8.2.1.2 获取分钟级别的数据¶

In [10]:
df = pro.daily(ts_code=stock_code, ktype = '5') #这里设置ktype参数为'5',即获取5分钟级别的数据,也可以将'5'换成'15'、'30'、'60'来获取15分钟、30分钟、60分钟级别的数据

df.head()
Out[10]:
ts_code trade_date open high low close pre_close change pct_chg vol amount
0 000002.SZ 20250317 7.60 7.73 7.54 7.56 7.54 0.02 0.2653 1290089.43 980824.633
1 000002.SZ 20250314 7.37 7.59 7.37 7.54 7.35 0.19 2.5850 1456521.92 1092327.785
2 000002.SZ 20250313 7.41 7.44 7.33 7.35 7.44 -0.09 -1.2097 758376.50 559845.298
3 000002.SZ 20250312 7.43 7.48 7.40 7.44 7.43 0.01 0.1346 661168.15 491951.065
4 000002.SZ 20250311 7.40 7.45 7.35 7.43 7.48 -0.05 -0.6684 872393.37 645348.200

8.2.2 股票衍生变量生成¶

8.2.2.1 获取股票基本数据¶

In [11]:
import tushare as ts

pro = ts.pro_api()

pro = ts.pro_api(token)
In [12]:
# 准备查询参数
stock_code = '000002.SZ'  # 股票代码,注意要加上后缀.SZ 表示深交所股票
start_date = '20150101'
end_date = '20191231'
In [13]:
df = pro.daily(ts_code=stock_code, start_date=start_date, end_date=end_date) #如果不写开始日期和结束日期, 会默认获取当天往前3年的数据

df.sort_values(by = 'trade_date', ascending= True, inplace=True)

df.head(5)
Out[13]:
ts_code trade_date open high low close pre_close change pct_chg vol amount
1083 000002.SZ 20150105 14.39 15.29 14.22 14.91 13.90 1.01 7.27 6560835.70 9.700712e+06
1082 000002.SZ 20150106 14.60 14.99 14.05 14.36 14.91 -0.55 -3.69 3346346.83 4.839616e+06
1081 000002.SZ 20150107 14.26 14.50 14.00 14.23 14.36 -0.13 -0.91 2642051.33 3.772151e+06
1080 000002.SZ 20150108 14.32 14.37 13.46 13.59 14.23 -0.64 -4.50 2639394.18 3.629554e+06
1079 000002.SZ 20150109 13.54 14.22 13.29 13.45 13.59 -0.14 -1.03 3294584.30 4.521978e+06

8.2.2.2 生成简单衍生变量¶

In [14]:
df['close-open'] = (df['close'] - df['open'])/df['open']
df['high-low'] = (df['high'] - df['low'])/df['low']

df['pre_close'] = df['close'].shift(1)  # 该列所有往下移一行形成昨日收盘价
df['price_change'] = df['close']-df['pre_close']
df['p_change'] = (df['close']-df['pre_close'])/df['pre_close']*100

df.head()
Out[14]:
ts_code trade_date open high low close pre_close change pct_chg vol amount close-open high-low price_change p_change
1083 000002.SZ 20150105 14.39 15.29 14.22 14.91 NaN 1.01 7.27 6560835.70 9.700712e+06 0.036136 0.075246 NaN NaN
1082 000002.SZ 20150106 14.60 14.99 14.05 14.36 14.91 -0.55 -3.69 3346346.83 4.839616e+06 -0.016438 0.066904 -0.55 -3.688799
1081 000002.SZ 20150107 14.26 14.50 14.00 14.23 14.36 -0.13 -0.91 2642051.33 3.772151e+06 -0.002104 0.035714 -0.13 -0.905292
1080 000002.SZ 20150108 14.32 14.37 13.46 13.59 14.23 -0.64 -4.50 2639394.18 3.629554e+06 -0.050978 0.067608 -0.64 -4.497540
1079 000002.SZ 20150109 13.54 14.22 13.29 13.45 13.59 -0.14 -1.03 3294584.30 4.521978e+06 -0.006647 0.069977 -0.14 -1.030169

8.2.2.3 生成移动平均线指标MA值¶

In [15]:
df['MA5'] = df['close'].rolling(5).mean()
df['MA10'] = df['close'].rolling(10).mean()

df.head(15)  # head(15)表示展示前15行,因为要展示10行以上,才能看到MA10有值
Out[15]:
ts_code trade_date open high low close pre_close change pct_chg vol amount close-open high-low price_change p_change MA5 MA10
1083 000002.SZ 20150105 14.39 15.29 14.22 14.91 NaN 1.01 7.27 6560835.70 9.700712e+06 0.036136 0.075246 NaN NaN NaN NaN
1082 000002.SZ 20150106 14.60 14.99 14.05 14.36 14.91 -0.55 -3.69 3346346.83 4.839616e+06 -0.016438 0.066904 -0.55 -3.688799 NaN NaN
1081 000002.SZ 20150107 14.26 14.50 14.00 14.23 14.36 -0.13 -0.91 2642051.33 3.772151e+06 -0.002104 0.035714 -0.13 -0.905292 NaN NaN
1080 000002.SZ 20150108 14.32 14.37 13.46 13.59 14.23 -0.64 -4.50 2639394.18 3.629554e+06 -0.050978 0.067608 -0.64 -4.497540 NaN NaN
1079 000002.SZ 20150109 13.54 14.22 13.29 13.45 13.59 -0.14 -1.03 3294584.30 4.521978e+06 -0.006647 0.069977 -0.14 -1.030169 14.108 NaN
1078 000002.SZ 20150112 13.32 13.32 12.75 13.12 13.45 -0.33 -2.45 2436341.36 3.180963e+06 -0.015015 0.044706 -0.33 -2.453532 13.750 NaN
1077 000002.SZ 20150113 13.05 13.38 12.97 13.07 13.12 -0.05 -0.38 1664610.33 2.187988e+06 0.001533 0.031611 -0.05 -0.381098 13.492 NaN
1076 000002.SZ 20150114 13.07 13.30 12.87 13.10 13.07 0.03 0.23 1646818.02 2.160268e+06 0.002295 0.033411 0.03 0.229533 13.266 NaN
1075 000002.SZ 20150115 13.13 13.83 13.00 13.77 13.10 0.67 5.11 2429686.64 3.265536e+06 0.048743 0.063846 0.67 5.114504 13.302 NaN
1074 000002.SZ 20150116 13.77 13.77 13.39 13.52 13.77 -0.25 -1.82 2129475.97 2.892247e+06 -0.018155 0.028379 -0.25 -1.815541 13.316 13.712
1073 000002.SZ 20150119 12.50 12.90 12.17 12.17 13.52 -1.35 -9.99 3603625.65 4.483885e+06 -0.026400 0.059984 -1.35 -9.985207 13.126 13.438
1072 000002.SZ 20150120 12.20 12.60 12.06 12.35 12.17 0.18 1.48 2914688.79 3.604938e+06 0.012295 0.044776 0.18 1.479047 12.982 13.237
1071 000002.SZ 20150121 12.33 13.20 12.10 13.05 12.35 0.70 5.67 3555294.15 4.554004e+06 0.058394 0.090909 0.70 5.668016 12.972 13.119
1070 000002.SZ 20150122 13.02 13.65 12.92 13.58 13.05 0.53 4.06 3224727.24 4.326298e+06 0.043011 0.056502 0.53 4.061303 12.934 13.118
1069 000002.SZ 20150123 13.57 14.09 13.30 13.92 13.58 0.34 2.50 3310408.58 4.561752e+06 0.025792 0.059398 0.34 2.503682 13.014 13.165
In [16]:
# 删除空值
df.dropna(inplace=True)  # 删除空值行,也可以写成df = df.dropna()
df.head(5)
Out[16]:
ts_code trade_date open high low close pre_close change pct_chg vol amount close-open high-low price_change p_change MA5 MA10
1074 000002.SZ 20150116 13.77 13.77 13.39 13.52 13.77 -0.25 -1.82 2129475.97 2.892247e+06 -0.018155 0.028379 -0.25 -1.815541 13.316 13.712
1073 000002.SZ 20150119 12.50 12.90 12.17 12.17 13.52 -1.35 -9.99 3603625.65 4.483885e+06 -0.026400 0.059984 -1.35 -9.985207 13.126 13.438
1072 000002.SZ 20150120 12.20 12.60 12.06 12.35 12.17 0.18 1.48 2914688.79 3.604938e+06 0.012295 0.044776 0.18 1.479047 12.982 13.237
1071 000002.SZ 20150121 12.33 13.20 12.10 13.05 12.35 0.70 5.67 3555294.15 4.554004e+06 0.058394 0.090909 0.70 5.668016 12.972 13.119
1070 000002.SZ 20150122 13.02 13.65 12.92 13.58 13.05 0.53 4.06 3224727.24 4.326298e+06 0.043011 0.056502 0.53 4.061303 12.934 13.118

8.2.2.5 用Ta-Lib库生成相对强弱指标的RSI值¶

RSI

  • RSI值能反映短期内股票涨势相对于跌势的强弱, 帮助判断股价的涨跌趋势, RSI值越大, 涨势相对于跌势更强, 反之则涨势相对于跌势越弱, 计算公式为 $$ RSI = \frac{N日平均上涨价格}{N日平均上涨价格 + N日平均下跌价格} \times 100 (\%) $$ 一般$N$的取值为6、12、24

  • 通常情况下RSI值位于20~80之间, 超过80则为超买状态 (股票买方处于非常强势的地位), 低于20则为超卖状态, 等于50则认为买卖双方力量均等

In [17]:
import talib

df["RSI"] = talib.RSI(df['close'], timeperiod=12)

8.2.2.6 用TA-Lib库生成动量指标MOM值¶

MOM

  • MOM是momentum (动量) 的缩写, 反映了一段时期内股价的涨跌速度, 计算公式为 $$MOM = C - C_n$$ 其中$C$表示当前的收盘价, $C_n$ 表示$n$天前的收盘价
In [18]:
df['MOM'] = talib.MOM(df['close'], timeperiod=5)

8.2.2.7 用TA-Lib库生成指数移动平均值EMA¶

EMA

  • 以指数式递减加权的移动平均, 并根据计算结果进行分析, 用于判断股价未来走势的变动趋势

  • 与移动平均线指标MA值优点类似, 不过计算公式更复杂

  • 计算公式为 $$EMA_{today} = \alpha Price_{today} + (1 - \alpha)EMA_{yesterday}$$ 其中$EMA_{today}$为当天的EMA值; $Price_{today}$为当天的收盘价; $EMA_{yesterday}$为昨天的EMA值; $\alpha$为平滑指数, 一般取 $\frac{2}{N+1}$, $N$表示天数, 当$N$为6时, $\alpha$为$\frac{2}{7}$, 对应的EMA称为EMA6, 即6日指数移动平均值

    公式不断递归, 直到第1个EMA值出现 (第一个EMA通常为开头五个数的均值)

  • 比如

    |date|1|2|3|4|5|6|7| |-|-|-|-|-|-|-|-| |price|1|1|1|1|1|1|1| |EMA|NAN|NAN|NAN|NAN|NAN|1|1.2857|

    取第1个EMA值为开头5个数的均值, 故前5天都没有EMA值; 6号的EMA值就是第1个EMA值, 为前5天的均值, 即1; 7号的EMA值为第2个EMA值, 计算过程为 $$EMA_{today} = \frac{2}{7} \times 2 + (1 - \frac{2}{7}) \times EMA_{yesterday} = \frac{2}{7} \times 2 + (1 - \frac{2}{7}) \times 1 = \frac{9}{7} \approx 1.2857$$

  • 对于EMA值而言, 近期的股价比之前更久远的股价更重要(在其计算公式中, 近期股价的权重更大), 不像MA值那样一视同仁

  • EMA12称为快的指数移动平均线, EMA26称为慢的指数移动平均线

In [19]:
df['EMA12'] = talib.EMA(df['close'], timeperiod=12)  # 12日指数移动平均线
df['EMA26'] = talib.EMA(df['close'], timeperiod=26)  # 26日指数移动平均线

8.2.2.8 用TA-Lib库生成异同移动平均线MACD值¶

  • MACD是股票市场上的常用指标, 它是基于EMA值的衍生变量, 计算方法比较复杂

  • 一种趋势类指标, 其变化代表着市场趋势的变化, 不同K线级别的MACD代表当前级别周期中的买卖趋势

  • MACD是从双指数移动平均值发展而来的, 计算步骤为

    • 计算EMA12和EMA26

    • 由快的指数移动平均线EMA12减去慢的指数移动平均线EMA26得到DIF值

      DIF = EMA12 - EMA26, 反映的是两条移动平均线的差值, DIF值对应的连线也称为快速线

      DEA值是DIF的9日加权移动均线值, 计算方法类似EMA9, 区别就是计算EMA9用的是收盘价, 而计算DEA值用的是DIF值

      DEA值对应的连线也称为慢速线

    • 再用2 × (DIF值 - DIF值的9日加权移动均线DEA值) 得到MACD值

      MCAD = 2 × (DIF - DEA), 也被称为MACD柱

      因此MACD技术指标图是由两线一柱组合而成的, 快速线是DIF值, 慢速线是DEA值, 柱形图为MACD值

8.2.3 多因子模型搭建¶

  • 本案例中的模型是根据多个特征进行搭建的, 在量化金融领域称为多因子模型

  • 股票数据是时间序列数据

8.2.3.1 引入需要用到的库¶

In [20]:
import tushare as ts  # 股票基本数据相关库
import numpy as np  # 科学计算相关库
import pandas as pd  # 科学计算相关库  
import talib  # 股票衍生变量数据相关库
import matplotlib.pyplot as plt  # 引入绘图相关库
from sklearn.ensemble import RandomForestClassifier  # 引入分类决策树模型
from sklearn.metrics import accuracy_score  # 引入准确度评分函数
import warnings
warnings.filterwarnings("ignore") # 忽略警告信息,警告非报错,不影响代码执行

8.2.3.2 获取数据¶

In [21]:
# 准备查询参数
stock_code = '000002.SZ'  # 股票代码,注意要加上后缀.SZ 表示深交所股票
start_date = '20150101'
end_date = '20191231'

pro = ts.pro_api()

pro = ts.pro_api(token)

df = pro.daily(ts_code=stock_code, start_date=start_date, end_date=end_date) #如果不写开始日期和结束日期, 会默认获取当天往前3年的数据

df['date'] = df['trade_date']

df = df.set_index('date')  # 设置日期为索引

df.sort_values(by='date', ascending=True, inplace=True)

df.head(5)
Out[21]:
ts_code trade_date open high low close pre_close change pct_chg vol amount
date
20150105 000002.SZ 20150105 14.39 15.29 14.22 14.91 13.90 1.01 7.27 6560835.70 9.700712e+06
20150106 000002.SZ 20150106 14.60 14.99 14.05 14.36 14.91 -0.55 -3.69 3346346.83 4.839616e+06
20150107 000002.SZ 20150107 14.26 14.50 14.00 14.23 14.36 -0.13 -0.91 2642051.33 3.772151e+06
20150108 000002.SZ 20150108 14.32 14.37 13.46 13.59 14.23 -0.64 -4.50 2639394.18 3.629554e+06
20150109 000002.SZ 20150109 13.54 14.22 13.29 13.45 13.59 -0.14 -1.03 3294584.30 4.521978e+06
In [22]:
# 简单衍生变量构造
df['close-open'] = (df['close'] - df['open'])/df['open']
df['high-low'] = (df['high'] - df['low'])/df['low']

df['pre_close'] = df['close'].shift(1)  # 该列所有往下移一行形成昨日收盘价
df['price_change'] = df['close']-df['pre_close']
df['p_change'] = (df['close']-df['pre_close'])/df['pre_close']*100

# 移动平均线相关数据构造
df['MA5'] = df['close'].rolling(5).mean()
df['MA10'] = df['close'].rolling(10).mean()
df.dropna(inplace=True)  # 删除空值

# 通过Ta_lib库构造衍生变量
df['RSI'] = talib.RSI(df['close'], timeperiod=12)  # 相对强弱指标
df['MOM'] = talib.MOM(df['close'], timeperiod=5)  # 动量指标
df['EMA12'] = talib.EMA(df['close'], timeperiod=12)  # 12日指数移动平均线
df['EMA26'] = talib.EMA(df['close'], timeperiod=26)  # 26日指数移动平均线
df['MACD'], df['MACDsignal'], df['MACDhist'] = talib.MACD(df['close'], fastperiod=12, slowperiod=26, signalperiod=9)  # MACD值
df.dropna(inplace=True)  # 删除空值
In [23]:
print(df.tail(5))

df['volume'] = df['vol']

print(df.columns)
            ts_code trade_date   open   high    low  close  pre_close  change  \
date                                                                            
20191225  000002.SZ   20191225  30.40  30.63  30.18  30.29      30.38   -0.09   
20191226  000002.SZ   20191226  30.50  31.30  30.50  31.12      30.29    0.83   
20191227  000002.SZ   20191227  31.23  31.32  30.81  31.00      31.12   -0.12   
20191230  000002.SZ   20191230  31.35  31.79  31.02  31.57      31.00    0.57   
20191231  000002.SZ   20191231  31.35  32.45  31.32  32.18      31.57    0.61   

          pct_chg        vol  ...  p_change     MA5    MA10        RSI   MOM  \
date                          ...                                              
20191225  -0.2962  685037.32  ... -0.296248  30.878  30.075  63.075563 -0.02   
20191226   2.7402  888790.74  ...  2.740178  30.896  30.387  68.890164  0.09   
20191227  -0.3856  703096.48  ... -0.385604  30.760  30.672  67.220611 -0.68   
20191230   1.8387  915751.42  ...  1.838710  30.872  30.884  70.877814  0.56   
20191231   1.9322  663497.98  ...  1.932214  31.232  31.057  74.233951  1.80   

              EMA12      EMA26      MACD  MACDsignal  MACDhist  
date                                                            
20191225  29.908556  28.973211  0.935345    0.772958  0.162387  
20191226  30.094932  29.132233  0.962699    0.810906  0.151793  
20191227  30.234173  29.270586  0.963587    0.841442  0.122145  
20191230  30.439685  29.440913  0.998772    0.872908  0.125864  
20191231  30.707426  29.643808  1.063618    0.911050  0.152567  

[5 rows x 24 columns]
Index(['ts_code', 'trade_date', 'open', 'high', 'low', 'close', 'pre_close',
       'change', 'pct_chg', 'vol', 'amount', 'close-open', 'high-low',
       'price_change', 'p_change', 'MA5', 'MA10', 'RSI', 'MOM', 'EMA12',
       'EMA26', 'MACD', 'MACDsignal', 'MACDhist', 'volume'],
      dtype='object')

8.2.3.3 提取特征变量和目标变量¶

In [24]:
X = df[['close', 'volume', 'close-open', 'MA5', 'MA10', 'high-low', 'RSI', 'MOM', 'EMA12', 'MACD', 'MACDsignal', 'MACDhist']]
y = np.where(df['price_change'].shift(-1)> 0, 1, -1) #

首先强调最核心的一点:应该是今天的股价信息预测下一天的股价涨跌情况,所以y应该是下一天的股价变化情况

其中Numpy库中的where()函数的使用方法如下所示: np.where(判断条件,满足条件的赋值,不满足条件的赋值)

其中df['price_change'].shift(-1)则是利用shift()函数将price_change(股价变化)这一列往上移动一行,这样就获得了每一行对应的下一天股价涨跌情况。

因此这里的判断条件就是下一天股价是否大于0,如果下一天股价涨了的我们则y赋值为数字1,下一天股价跌了的,则y赋值为数字-1。这个下一天的股价涨跌情况就是我们根据当天股票基本数据以及衍生变量预测的内容。

8.2.3.4 划分训练集和测试集¶

训练集与测试集的划分要按照时间序列划分,而不是像之前利用train_test_split()函数进行划分。原因在于股票价格的变化趋势具有时间性,如果我们随机划分,则会破坏时间性特征,因为我们是根据当天数据来预测下一天的股价涨跌情况,而不是任意一天的股票数据来预测下一天的股价涨跌情况。

因此,将前90%的数据作为训练集,后10%的数据作为测试集

In [25]:
X_length = X.shape[0]  # shape属性获取X的行数和列数,shape[0]即表示行数 
split = int(X_length * 0.9)

X_train, X_test = X[:split], X[split:]
y_train, y_test = y[:split], y[split:]

8.2.3.5 模型搭建¶

In [26]:
model = RandomForestClassifier(max_depth=3, n_estimators=10, min_samples_leaf=10, random_state=1) #max_depth=3设置决策树的最大深度为3, 即每个决策树最多只有3层; n_estimators=10设置弱学习器的个数为10, 即该随机森林中共有10个决策树; min_samples_leaf=10设置叶子节点的最小样本数为10, 即如果叶子节点的样本数小于10则停止分裂
model.fit(X_train, y_train)
Out[26]:
RandomForestClassifier(max_depth=3, min_samples_leaf=10, n_estimators=10,
                       random_state=1)
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
RandomForestClassifier(max_depth=3, min_samples_leaf=10, n_estimators=10,
                       random_state=1)

8.2.4 模型使用与评估¶

8.2.4.1 预测下一天的股价涨跌情况¶

In [27]:
y_pred = model.predict(X_test)
print(y_pred)
[-1 -1 -1 -1  1 -1 -1 -1  1 -1  1  1 -1  1 -1 -1 -1  1  1 -1 -1 -1  1  1
 -1  1 -1  1 -1 -1 -1  1 -1  1  1 -1  1 -1  1  1 -1 -1  1 -1 -1 -1  1 -1
 -1  1 -1 -1  1 -1  1  1 -1 -1 -1  1 -1  1 -1 -1 -1  1 -1 -1  1 -1 -1 -1
 -1 -1 -1 -1 -1 -1 -1 -1 -1  1  1 -1 -1 -1  1 -1 -1  1 -1 -1 -1 -1 -1 -1
 -1 -1 -1 -1 -1 -1 -1 -1 -1]
In [28]:
a = pd.DataFrame()  # 创建一个空DataFrame 
a['预测值'] = list(y_pred)
a['实际值'] = list(y_test)
print(a.head())
   预测值  实际值
0   -1   -1
1   -1   -1
2   -1   -1
3   -1   -1
4    1    1
In [29]:
y_pred_proba = model.predict_proba(X_test)

y_pred_proba = pd.DataFrame(y_pred_proba, columns=['预测为-1的概率', '预测为1的概率'])#-1表示下一天股价不变或下跌, 1表示下一天股价上涨

print(y_pred_proba)
     预测为-1的概率   预测为1的概率
0    0.565460  0.434540
1    0.505614  0.494386
2    0.528330  0.471670
3    0.528330  0.471670
4    0.480140  0.519860
..        ...       ...
100  0.565460  0.434540
101  0.565460  0.434540
102  0.528510  0.471490
103  0.587631  0.412369
104  0.596026  0.403974

[105 rows x 2 columns]

8.2.4.2 模型准确度评估¶

In [30]:
from sklearn.metrics import accuracy_score
score = accuracy_score(y_pred, y_test)
print(score)

model.score(X_test, y_test)
0.5428571428571428
Out[30]:
0.5428571428571428

8.2.4.3 分析特征变量的特征重要性¶

In [31]:
# 通过如下代码可以更好的展示特征及其特征重要性:
features = X.columns  
importances = model.feature_importances_
a = pd.DataFrame()
a['特征'] = features
a['特征重要性'] = importances
a = a.sort_values('特征重要性', ascending=False)
print(a)
            特征     特征重要性
7          MOM  0.177669
0        close  0.149504
10  MACDsignal  0.110953
5     high-low  0.104257
1       volume  0.096303
4         MA10  0.088574
2   close-open  0.078953
3          MA5  0.069882
9         MACD  0.066631
11    MACDhist  0.033678
8        EMA12  0.019082
6          RSI  0.004513

8.2.5 参数调优¶

In [32]:
from sklearn.model_selection import GridSearchCV  # 网格搜索合适的超参数
# 指定分类器中参数的范围
parameters = {'n_estimators':[5, 10, 20], 'max_depth':[2, 3, 4, 5], 'min_samples_leaf':[5, 10, 20, 30]}
new_model = RandomForestClassifier(random_state=1)  # 构建分类器
grid_search = GridSearchCV(new_model, parameters, cv=6, scoring='accuracy')  # cv=6表示交叉验证6次,scoring='roc_auc'表示以ROC曲线的AUC评分作为模型评价准则, 默认为'accuracy', 即按准确度评分
In [33]:
grid_search.fit(X_train, y_train)  # 传入数据
grid_search.best_params_  # 输出参数的最优值
Out[33]:
{'max_depth': 3, 'min_samples_leaf': 30, 'n_estimators': 10}

8.2.6 收益回测曲线绘制¶

  • 相比于模型的预测准确度, 在商业实战中, 更关心收益回测曲线, 也就是看根据搭建的模型获得的结果是否比不利用模型获得的结果更好

  • 根据当天的股价预测数据预测下一天的股价涨跌情况, 如果预测为1, 则在下一天买入, 否则卖出, 所以这里通过shift(1)将预测结果这一列向下移动一行, 这样才能和下一天的股价变化率相匹配, 再用cumprod()函数来计算收益率

In [34]:
X_test['prediction'] = model.predict(X_test)
X_test['p_change'] = (X_test['close'] - X_test['close'].shift(1)) / X_test['close'].shift(1)

X_test['origin'] = (X_test['p_change'] + 1).cumprod()
X_test['strategy'] = (X_test['prediction'].shift(1) * X_test['p_change'] + 1).cumprod()

print(X_test[['strategy', 'origin']].tail())
          strategy    origin
date                        
20191225  1.113699  1.015761
20191226  1.083182  1.043595
20191227  1.087359  1.039571
20191230  1.067365  1.058685
20191231  1.046741  1.079142
In [35]:
# 通过如下代码将收益情况删除空值后可视化,并设置X轴刻度自动倾斜:
X_test[['strategy', 'origin']].dropna().plot()
plt.gcf().autofmt_xdate()
plt.show()
No description has been provided for this image